package cn.bw;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;

public class NioServer {

    private static final Long TIME_OUT = 3000L;

    private static final Logger log = LoggerFactory.getLogger(NioServer.class);

    public static void main(String[] args) throws IOException {
        start();
    }

    public static void start() throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(ConstantKit.SERVER_PORT));
        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        log.info("服务启动于[{}]成功，正在监听事件",
                serverSocketChannel.getLocalAddress());
        // 循环监听
        while (!Thread.currentThread().isInterrupted()) {
            if (selector.select(TIME_OUT) > 0) {
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> readyKeys = selectionKeys.iterator();
                while (readyKeys.hasNext()) {
                    SelectionKey key = readyKeys.next();
                    readyKeys.remove();
                    SocketChannel socketChannel = null;
                    try {
                        // 连接
                        if (key.isValid() && key.isAcceptable()) {
                            socketChannel = ((ServerSocketChannel) key.channel()).accept();
                            socketChannel.configureBlocking(false);
                            log.info("Accept 来自于[{}]的连接请求", socketChannel.getRemoteAddress());
                            // 将客户端的可读事件注册到selector中监听
                            socketChannel.register(key.selector(), SelectionKey.OP_READ);
                        }
                        // 数据可读
                        if (key.isValid() && key.isReadable()) {
                            socketChannel = (SocketChannel) key.channel();
                            log.info("Readable 客户端[{}]发送了数据", socketChannel.getRemoteAddress());
                            getRequestData(key);
                        }
                        if (key.isValid() && key.isWritable()) {
                            doResponse(key);
                        }
                    } catch (IOException e) {
                        log.error("服务端IO异常;", e);
                        if (!Objects.isNull(socketChannel)) {
                            socketChannel.close();
                            key.cancel();
                        }
                    }
                }
            }
        }
        // 关闭channel
        serverSocketChannel.close();
        selector.close();
        log.info("进程退出;");
    }

    /**
     * 处理连接请求
     *
     * @param selectionKey key
     * @throws IOException 异常
     */
    private static void getRequestData(SelectionKey selectionKey) throws IOException {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        // 从socketChannel中读出数据
        try {
            if (socketChannel.isOpen()) {
                // 将写事件注册到selector
                socketChannel.register(selectionKey.selector(), selectionKey.interestOps()
                        | SelectionKey.OP_WRITE);
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                StringBuffer content = new StringBuffer();
                int readBytes = 0;
                while ((readBytes = socketChannel.read(byteBuffer)) > 0) {
                    byteBuffer.flip();
                    byte[] temp = new byte[readBytes];
                    byteBuffer.get(temp, 0, readBytes);
                    content.append(new String(temp, StandardCharsets.UTF_8));
                    byteBuffer.clear();
                }
                // 判断客户端是否断开连接
                // 保持长连接的方式：
                // 当客户端没有断开连接的时候，readBytes=0
                // 当客户端已经断开连接的时候，readBytes=-1
                if (readBytes < 0) {
                    log.info("远程客户端[{}]断开连接;", socketChannel.getRemoteAddress());
                    socketChannel.close();
                    selectionKey.cancel();
                } else {
                    log.info("收到的内容是:{}", content);
                }
            } else {
                log.warn("channel 已经关闭;");
                socketChannel.shutdownInput();
                socketChannel.shutdownOutput();
                socketChannel.close();
                selectionKey.cancel();
            }
        } catch (Exception e) {
            log.error("客户端[" + selectionKey + "]错误;", e);
            socketChannel.shutdownInput();
            socketChannel.shutdownOutput();
            selectionKey.cancel();
            log.info("socketChannel[{}]已经从selector取消注册;", socketChannel.getRemoteAddress());
            socketChannel.close();
        }
    }

    // 返回响应数据
    private static void doResponse(SelectionKey selectionKey) throws IOException {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        log.info("回复数据'{}'到[{}]", "这是数据", socketChannel.getRemoteAddress());
        socketChannel.write(ByteBuffer.wrap("这是数据".getBytes(StandardCharsets.UTF_8)));
        socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ);
    }
}
